﻿using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using DeepEarth.Core;
using DeepEarth.Core.Persistence;
using DeepEarth.Map.Core;
using DeepEarth.Toolkit.Common;
using Point = System.Windows.Point;

namespace DeepEarth.Toolkit.Controls
{
    [TemplatePart(Name = PART_Layoutroot, Type = typeof(FrameworkElement))]
    public class CoordinatePanel : MapInteractionControl
    {
        const string PART_Layoutroot = "PART_Layoutroot";

        Point lastMouseLocation;
        FrameworkElement layoutRoot;

        public CoordinatePanel()
        {
            lastMouseLocation = new Point();
            DefaultStyleKey = typeof(CoordinatePanel);
        }

        static bool DifferentWithTolerance(double value1, double value2, double tolerance)
        {
            return !(value1 + tolerance > value2 && value1 - tolerance < value2);
        }

        #region Properties
        public static readonly DependencyProperty ModeProperty = DependencyProperty.Register("Mode", typeof(CoordinateSystem), typeof(CoordinatePanel), new PropertyMetadata(CoordinateSystem.Degrees, OnModePropertyChanged));
        [Persist]
        public CoordinateSystem Mode
        {
            get { return (CoordinateSystem)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        static void OnModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = d as CoordinatePanel;
            if (ctrl != null)
                ctrl.OnModePropertyChanged(e);
        }

        void OnModePropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (Location != null)
            {
                var existingLocation = Location;
                Location = new LocationWithFormat
                {
                    Altitude = existingLocation.Altitude,
                    Latitude = existingLocation.Latitude,
                    LocationFormat = (CoordinateSystem)e.NewValue,
                    Longitude = existingLocation.Longitude
                };
            }
        }

        public static readonly DependencyProperty LocationProperty = DependencyProperty.Register("Location", typeof(LocationWithFormat), typeof(CoordinatePanel), new PropertyMetadata(OnLocationPropertyChanged));
        public LocationWithFormat Location
        {
            get { return (LocationWithFormat)GetValue(LocationProperty); }
            set { SetValue(LocationProperty, value); }
        }

        static void OnLocationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = d as CoordinatePanel;
            if (ctrl != null)
                ctrl.OnLocationPropertyChanged(e);
        }

        void OnLocationPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            //do we need to update the map?
            //validate input, set map centre if sig different from previous value.
            var loc = e.NewValue as LocationWithFormat;
            const double tolerance = 0.0005;
            if (loc != null && mapLocation != null &&
                (DifferentWithTolerance(loc.Latitude, mapLocation.Latitude, tolerance) ||
                    DifferentWithTolerance(loc.Longitude, mapLocation.Longitude, tolerance)))
            {
                Map.View.Center = new Location(loc.Latitude, loc.Longitude);
            }
        }

        public static readonly DependencyProperty ZoomLevelProperty = DependencyProperty.Register("ZoomLevel", typeof(string), typeof(CoordinatePanel), new PropertyMetadata(OnZoomLevelPropertyChanged));
        public string ZoomLevel
        {
            get { return (string)GetValue(ZoomLevelProperty); }
            set { SetValue(ZoomLevelProperty, value); }
        }

        static void OnZoomLevelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = d as CoordinatePanel;
            if (ctrl != null)
                ctrl.OnZoomLevelPropertyChanged(e);
        }

        void OnZoomLevelPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            //do we need to update the map?
            //validate input, set zoom.
            double zoom;
            if (double.TryParse(e.NewValue.ToString(), out zoom))
            {
                if (zoom <= 25 && zoom >= 1)
                    Map.ZoomLevel = zoom;
            }
        }

        public static readonly DependencyProperty LockMapUpdatesProperty = DependencyProperty.Register("LockMapUpdates", typeof(bool), typeof(CoordinatePanel), new PropertyMetadata(false));
        public bool LockMapUpdates
        {
            get { return (bool)GetValue(LockMapUpdatesProperty); }
            set { SetValue(LockMapUpdatesProperty, value); }
        }
        #endregion

        protected override void MapChanged(IMap newMap, IMap oldMap)
        {
            if (oldMap != null)
            {
                oldMap.View.PropertyChanged -= CurrentView_PropertyChanged;
                oldMap.AsFrameworkElement.MouseMove -= map_MouseMove;
            }

            if (newMap.View != null)
            {
                newMap.View.PropertyChanged += CurrentView_PropertyChanged;
            }
            newMap.AsFrameworkElement.MouseMove += map_MouseMove;
        }

        void CurrentView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            var property = e.PropertyName;

            switch (property)
            {
                case MapView.CENTER :
                    RefreshCoordinates(Map.LocationToViewportPoint(Map.View.Center));
                    break;
                case MapView.VIEWPORT_WIDTH :
                case MapView.ZOOM_LEVEL :
                    RefreshZoomLevel();
                    break;
            }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            layoutRoot = (FrameworkElement) GetTemplateChild(PART_Layoutroot);

            if (layoutRoot != null)
                layoutRoot.DataContext = this;

            //set inital values
            RefreshCoordinates(lastMouseLocation);
            RefreshZoomLevel();
        }

        void map_MouseMove(object sender, MouseEventArgs e)
        {
            if (!LockMapUpdates)
                RefreshCoordinates(e.GetPosition(Map.AsFrameworkElement));
        }

        void RefreshZoomLevel()
        {
            if (Map != null)
                ZoomLevel = string.Format("{0:N2}", Map.ZoomLevel);
        }

        Location mapLocation;
        void RefreshCoordinates(Point point)
        {
            if (Map != null)
            {
                mapLocation = Map.ViewportPointToLocation(point);
                Location = new LocationWithFormat
                               {
                                   Altitude = mapLocation.Altitude,
                                   Latitude = mapLocation.Latitude,
                                   LocationFormat = Mode,
                                   Longitude = mapLocation.Longitude
                               };
            }
        }
    }
}